package com.atguigu.gmall.common.config;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.entity.GmallCorrelationData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.PostConstruct;

/**
 * @author 李旭
 * @date 2021/12/1 15:00
 * @Description:
 *
 *   MQ的生产者Java配置类
 *
 *   spring.xml
 *     <bean id=  class=
 *
 *     代码执行顺序
 *        1、 @Configuration  进行实例化 （加载到SpringIOC容器）
 *           MQProduceConfig
 *        2、创建MQProduceConfig
 *        3、构造器执行
 *        4、注入
 *        5、后置
 *
 *        Spring名词：
 *            前置
 *            方法
 *            后置
 *            异常
 *
 *         Spring的Bean的生命周期中
 *             1、对象的创建
 *             2、对象的前置
 *             3、对象的初始化
 *             4、对象的后置
 *             5、对象的销毁
 *
 *
 *
 */
@Configuration
@Slf4j
public class MQProduceConfig implements
                    RabbitTemplate.ConfirmCallback,
                    RabbitTemplate.ReturnCallback {// 实现 内部类的接口
    //注入
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    //MQ的消息的散列类型的Key
    public static final String RABBITMQ_RETRYMESSAGE  = "rabbitmq:retryMessage";
    //后置
    @PostConstruct //后置注解
    public void abd(){
        rabbitTemplate.setConfirmCallback(this);//交换机应答接口
        rabbitTemplate.setReturnCallback(this);//队列应答接口
    }

    //构造器
/*    public MQProduceConfig(){
        rabbitTemplate.setConfirmCallback(this);//交换机应答接口
        rabbitTemplate.setReturnCallback(this);//队列应答接口
    }*/
    /**
     * 交换机成功或失败应答的接收方法
     * @param correlationData : 应答对象 （交换机、RoutingKey 、Message）
     * @param ack   应答状态  ack:true 成功  ack:false 失败
     * @param cause 应答原因   成功 cause是null   |   cause不是null  失败的原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if(ack){
            log.info("交换机应答成功，原因是:{}",cause);
        }else{
            log.error("交换机应答失败,原因是:{}",cause);

            //重新发送消息
            retrySendMessage(correlationData);
        }
    }
    //重新发消息  交换机、RoutingKey 、Message
    private void retrySendMessage(CorrelationData correlationData) {
        //自定义的子类 的关联数据对象
        GmallCorrelationData gmallCorrelationData = (GmallCorrelationData)correlationData;
        //要求重新发送 2次或3次 还是发送失败 打日志
        int retryCount = gmallCorrelationData.getRetryCount();
        if(retryCount < 2){
            retryCount++;
            gmallCorrelationData.setRetryCount(retryCount);
            log.info("重新发送消息:{}", JSONObject.toJSONString(gmallCorrelationData));
            //更新缓存中  子类的关联数据对象  发送的次数发生变化了
            redisTemplate.opsForHash().put(RABBITMQ_RETRYMESSAGE,gmallCorrelationData.getId(),
                    JSONObject.toJSONString(gmallCorrelationData));
            //重新发送消息
            rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(),
                    gmallCorrelationData.getRoutingKey()
                    ,gmallCorrelationData.getMessage(),(message) -> {
                message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.fromInt(2));
                return message;
            },gmallCorrelationData);
        }else{
            log.error("重新发送的次数已经耗尽了,{}",JSONObject.toJSONString(gmallCorrelationData));
            //转人工
        }


    }
    /**
     * 队列失败的时候 应答的接收方法
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message,
                                int replyCode,
                                String replyText,
                                String exchange,
                                String routingKey) {
        log.error("队列应答失败:{}",replyText);
        //问题1：发送次数  无法记录发送的次数
        //问题2：没有关联数据对象  重新发送消息的之后 可能会造成交换机应答失败 失败之后无法再重新发消息
        //解决： 获取Redis缓存中的关联数据对象
        Object uuid = message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");
        String jsonData = (String) redisTemplate.opsForHash().get(RABBITMQ_RETRYMESSAGE, uuid);
        GmallCorrelationData gmallCorrelationData = JSONObject.
                parseObject(jsonData, GmallCorrelationData.class);
        //判断是否为基于插件的延迟消息
        if(gmallCorrelationData.isDelay()){
            log.info("是基于插件的延迟消息的队列应答，可以不重新发消息");
            return ;
        }

        //重新发送消息
        retrySendMessage(gmallCorrelationData);

    }
}
